/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 adv_object_model.h

	$Header: /heroes4/adv_object_model.h $

	$NoKeywords: $

 ************************************************************************/

#if !defined( ADV_OBJECT_MODEL_H_INCLUDED )
#define ADV_OBJECT_MODEL_H_INCLUDED

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#pragma warning( disable: 4786 ) // Disable the ever so helpful "identifier was truncated to '255' characters" warning

#include <algorithm>
#include <cassert>
#include <iosfwd>
#include <vector>
#include <typeinfo>

#include "adv_object_image.h"
#include "checked_cast.h"
#include "footprint.h"
#include "map_point.h"
#include "qualified_adv_object_type.h"
#include "resource_traits.h"
#include "screen_point.h"
#include "shared_ptr.h"

struct t_screen_point;

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_flatten_type enum

enum t_adv_object_flatten_type
{
	k_adv_object_flatten_none,
	k_adv_object_flatten_impassable,
	k_adv_object_flatten_all,

	k_adv_object_flatten_type_count,
};

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model_base_base class

class t_adv_object_model_base_base : public t_footprint
{
public:
	// Types
	typedef t_adv_object_flatten_type	t_flatten_type;
	typedef t_qualified_adv_object_type	t_qualified_type;

	// Destructor
	t_adv_object_model_base_base();
	virtual ~t_adv_object_model_base_base();

	// Member functions
	virtual bool				are_any_cells_flat() const;
	virtual bool				are_any_cells_impassable() const;
	t_screen_point const &		get_flag_offset() const;
	t_flatten_type				get_flatten_type() const;
	int							get_frames_per_second() const;
	t_qualified_type const &	get_qualified_type() const;
	virtual t_map_point_2d		get_size() const;
	virtual int					get_vertex_height( t_map_point_2d const& point ) const;
	bool						has_flag() const;
	virtual bool				is_cell_flat( t_map_point_2d const & point ) const;
	virtual bool				is_cell_impassable( t_map_point_2d const & point ) const;
	virtual bool				is_left_edge_blocked( t_map_point_2d const& point,
												      bool				    under_bridge ) const;
	virtual bool				is_left_edge_trigger( t_map_point_2d const & point ) const;
	virtual bool				is_right_edge_trigger( t_map_point_2d const & point ) const;
	virtual bool				is_right_edge_blocked( t_map_point_2d const& point,
												       bool				     under_bridge ) const;
	virtual bool				is_trigger_cell( t_map_point_2d const & point ) const;
	void						resize( t_map_point_2d const & new_size );
	void						set_cell_impassable( t_map_point_2d const & point,
													 bool impassable );
	void						set_edge_blocked( t_map_point_2d const& point, bool under_bridge,
												  bool right_edge, bool blocked );
	void						set_flag_offset( t_screen_point const & new_flag_offset );
	void						set_flatten_type( t_flatten_type new_flatten_type );
	void						set_frames_per_second( int new_frames_per_second );
	void						set_has_flag( bool new_has_flag );
	void						set_left_edge_blocked( t_map_point_2d const& point,
													   bool under_bridge, bool   blocked );
	void						set_left_edge_trigger( t_map_point_2d const & point, bool trigger );
	void						set_qualified_type( t_qualified_type const & new_type );
	void						set_right_edge_blocked( t_map_point_2d const& point,
													    bool under_bridge, bool   blocked );
	void						set_right_edge_trigger( t_map_point_2d const & point, bool trigger );
	void						set_vertex_height( t_map_point_2d const& point, int value );

protected:
	// Member functions
	bool read( std::streambuf & stream, int format_version );
	bool write( std::streambuf & stream ) const;

private:
	// Data members
	std::vector< bool >		m_cell_impassable_vector;
	t_screen_point			m_flag_offset;
	t_flatten_type			m_flatten_type;
	int						m_frames_per_second;
	bool					m_has_flag;
	std::vector< bool >		m_left_edge_blocked;
	std::vector< bool >		m_left_edge_trigger_vector;
	t_qualified_type		m_qualified_type;
	std::vector< bool >		m_right_edge_blocked;
	std::vector< bool >		m_right_edge_trigger_vector;
	t_map_point_2d			m_size;
	std::vector<t_uint8>	m_vertex_height;

	// Member functions
	bool are_any_cells_impassable_impl() const;
	bool is_cell_impassable_impl( t_map_point_2d const & point ) const;
	bool is_left_edge_trigger_impl( t_map_point_2d const & point ) const;
	bool is_right_edge_trigger_impl( t_map_point_2d const & point ) const;
	int  get_blocked_left_edge_index( t_map_point_2d const& point, bool to_ships ) const;
	int  get_blocked_right_edge_index( t_map_point_2d const& point, bool to_ships ) const;
	bool write_footprint( std::streambuf & stream ) const;
};

inline t_adv_object_model_base_base::t_adv_object_model_base_base()
	:	m_frames_per_second( 10 ),
		m_flatten_type( k_adv_object_flatten_impassable ),
		m_size( 1, 1 ),
		m_cell_impassable_vector( 1, false ),
		m_left_edge_blocked( 4, false ),
		m_left_edge_trigger_vector( 2, false ),
		m_right_edge_blocked( 4, false ),
		m_right_edge_trigger_vector( 2, false ),
		m_has_flag( false ),
		m_flag_offset( 0, 0 ),
		m_vertex_height( 4, false )
{
}

inline t_adv_object_model_base_base::~t_adv_object_model_base_base()
{
}

inline t_screen_point const &
t_adv_object_model_base_base::get_flag_offset() const
{
	assert( m_has_flag );
	return m_flag_offset;
}

inline t_adv_object_model_base_base::t_flatten_type
t_adv_object_model_base_base::get_flatten_type() const
{
	return m_flatten_type;
}

inline int t_adv_object_model_base_base::get_frames_per_second() const
{
	return m_frames_per_second;
}

inline t_adv_object_model_base_base::t_qualified_type const &
t_adv_object_model_base_base::get_qualified_type() const
{
	return m_qualified_type;
}

inline bool t_adv_object_model_base_base::has_flag() const
{
	return m_has_flag;
}

inline void t_adv_object_model_base_base::set_cell_impassable(
	t_map_point_2d const &	point,
	bool					impassable )
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );
	m_cell_impassable_vector[ ( point.column * m_size.row ) + point.row ] = impassable;
}

inline void t_adv_object_model_base_base::set_edge_blocked( t_map_point_2d const& point,
															bool sea, bool right_edge,
															bool blocked )
{
	if (right_edge)
		set_right_edge_blocked( point, sea, blocked );
	else
		set_left_edge_blocked( point, sea, blocked );
}

inline void t_adv_object_model_base_base::set_flag_offset(
	t_screen_point const &	new_flag_offset )
{
	assert( m_has_flag );
	m_flag_offset = new_flag_offset;
}

inline void t_adv_object_model_base_base::set_flatten_type(
	t_flatten_type	new_flatten_type )
{
	assert( new_flatten_type >= 0 && new_flatten_type < k_adv_object_flatten_type_count );
	m_flatten_type = new_flatten_type;
}

inline void t_adv_object_model_base_base::set_frames_per_second(
	int	new_frames_per_second )
{
	assert( new_frames_per_second > 0 && new_frames_per_second <= 50 );
	m_frames_per_second = new_frames_per_second;
}

inline void t_adv_object_model_base_base::set_has_flag( bool new_has_flag )
{
	m_has_flag = new_has_flag;
}

inline void t_adv_object_model_base_base::set_left_edge_trigger(
	t_map_point_2d const &	point,
	bool					trigger )
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column <= m_size.column );
	m_left_edge_trigger_vector[ ( point.column * m_size.row ) + point.row ] = trigger;
}

inline void t_adv_object_model_base_base::set_qualified_type(
	t_qualified_type const &	new_type )
{
	m_qualified_type = new_type;
}

inline void t_adv_object_model_base_base::set_right_edge_trigger(
	t_map_point_2d const &	point,
	bool					trigger )
{
	assert( point.row >= 0 && point.row <= m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );
	m_right_edge_trigger_vector[ ( point.row * m_size.column ) + point.column ] = trigger;
}

inline bool t_adv_object_model_base_base::is_cell_impassable_impl(
	t_map_point_2d const &	point ) const
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );
	return m_cell_impassable_vector[ ( point.column * m_size.row ) + point.row ];
}

inline bool t_adv_object_model_base_base::is_left_edge_trigger_impl(
	t_map_point_2d const &	point ) const
{
	assert( point.row >= 0 && point.row < m_size.row );
	assert( point.column >= 0 && point.column <= m_size.column );
	return m_left_edge_trigger_vector[ ( point.column * m_size.row ) + point.row ];
}

inline bool t_adv_object_model_base_base::is_right_edge_trigger_impl(
	t_map_point_2d const &	point ) const
{
	assert( point.row >= 0 && point.row <= m_size.row );
	assert( point.column >= 0 && point.column < m_size.column );
	return m_right_edge_trigger_vector[ ( point.row * m_size.column ) + point.column ];
}

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model_base class template

template < typename t_image_arg >
class t_adv_object_model_base : public t_adv_object_model_base_base
{
public:
	// Types
	typedef t_image_arg t_image;

	// Constructors/Destructor
	t_adv_object_model_base();
	t_adv_object_model_base( t_shared_ptr< t_image > image_ptr, t_adv_object_model_base_base const & base );

	// Member functions
	t_image const & get_image() const;
	void set_image( t_shared_ptr< t_image > new_image_ptr );
	void toggle_base_frame_draw(bool val);

private:
	// Data members
	t_shared_ptr< t_image >		m_image_ptr;
};

template < typename t_image_arg >
inline t_adv_object_model_base< t_image_arg >::t_adv_object_model_base()
{
}

template < typename t_image_arg >
inline t_adv_object_model_base< t_image_arg >::t_adv_object_model_base( t_shared_ptr< t_image > image_ptr, t_adv_object_model_base_base const & base )
	:	t_adv_object_model_base_base( base ),
		m_image_ptr( image_ptr )
{
}

template < typename t_image_arg >
inline typename t_adv_object_model_base< t_image_arg >::t_image const & t_adv_object_model_base< t_image_arg >::get_image() const
{
	assert( m_image_ptr.get() != 0 );
	return *m_image_ptr;
}

template < typename t_image_arg >
inline void t_adv_object_model_base< t_image_arg >::set_image( t_shared_ptr< t_image > new_image_ptr )
{
	m_image_ptr = new_image_ptr;
}

template < typename t_image_arg >
inline void t_adv_object_model_base< t_image_arg >::toggle_base_frame_draw(bool val)
{
	m_image_ptr->toggle_base_frame_draw(val);
}

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model_24 class

class t_adv_object_model_24 : public t_adv_object_model_base< t_adv_object_image_24 >
{
public:
	// Member functions
	bool read( std::streambuf & stream );
	bool write( std::streambuf & stream ) const;
};

inline bool read( std::streambuf & stream, t_adv_object_model_24 & model )
{
	return model.read( stream );
}

/////////////////////////////////////////////////////////////////////////////
// t_adv_object_model class

class t_adv_object_model : public t_adv_object_model_base< t_adv_object_image >
{
public:
	// Constructor
	t_adv_object_model( t_adv_object_model_24 const & other );
};

/////////////////////////////////////////////////////////////////////////////
// t_resource_traits< t_adv_object_model_24 > specialization

template <>
struct t_resource_traits< t_adv_object_model_24 >
{
	static char const prefix[];
	static char const extension[];
};

#endif // !defined( ADV_OBJECT_MODEL_H_INCLUDED )
